/**
 * Copyright (c) 2014, Ford Motor Company
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided with the
 * distribution.
 *
 * Neither the name of the Ford Motor Company nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "cppgen/cpp_file.h"

#include <cassert>
#include <ostream>

#include "cppgen/naming_convention.h"
#include "model/interface.h"
#include "model/type.h"
#include "utils/safeformat.h"

using std::ostream;
using std::endl;
using typesafe_format::strmfmt;

namespace codegen {

namespace {
const char* kAutoGeneratedComment = "// This file is generated, do not edit";

// This class is used to find and automatically include appropriate
// header file where given type is defined
class IncludeTypeHelper: public TypeCodeGenerator {
 public:
  // Constructs include helper and immediately includes
  // appropriate header file for passed |type|
  // into |cpp_file|
  IncludeTypeHelper(CppFile* cpp_file, const Type* type);
 private:
  // Methods
  // Returns interface name where given |type| is defined
  // Accepts Enum, Struct or Typedef types.
  template<class T>
  std::string GetTypeInterfaceName(const T* type);
  // TypeCodeGenerator interface
  virtual void GenerateCodeForArray(const Array* array);
  virtual void GenerateCodeForMap(const Map* map);
  virtual void GenerateCodeForEnum(const Enum* enm);
  virtual void GenerateCodeForNullable(const NullableType* nullable);
  virtual void GenerateCodeForStruct(const Struct* strct);
  virtual void GenerateCodeForTypedef(const Typedef* tdef);
 private:
  // Fields
  CppFile* cpp_file_;
};

IncludeTypeHelper::IncludeTypeHelper(CppFile* cpp_file, const Type* type)
  : cpp_file_(cpp_file) {
  type->Apply(this);
}

template<class T>
std::string IncludeTypeHelper::GetTypeInterfaceName(const T* type) {
  return LowercaseIntefaceName(type->interface());
}

void IncludeTypeHelper::GenerateCodeForArray(const Array* array) {
  array->type()->Apply(this);
}

void IncludeTypeHelper::GenerateCodeForMap(const Map* map) {
  map->type()->Apply(this);
}

void IncludeTypeHelper::GenerateCodeForEnum(const Enum *enm) {
  cpp_file_->Include(CppFile::Header(
                       GetTypeInterfaceName(enm) + "/enums.h",
                       true));
}

void IncludeTypeHelper::GenerateCodeForNullable(const NullableType* nullable) {
  nullable->type()->Apply(this);
}

void IncludeTypeHelper::GenerateCodeForStruct(const Struct* strct) {
  cpp_file_->Include(CppFile::Header(
                       GetTypeInterfaceName(strct) + "/types.h",
                       true));
}

void IncludeTypeHelper::GenerateCodeForTypedef(const Typedef* tdef) {
  cpp_file_->Include(CppFile::Header(
                       GetTypeInterfaceName(tdef) + "/types.h",
                       true));
}

}

CppFile::CppFile(const std::string& file_name, const std::string& module_name,
                 bool write_guards)
    : write_guards_(write_guards),
      file_name_(file_name),
      module_name_(module_name) {
}

CppFile::~CppFile() {
}

const std::string& CppFile::file_name() const {
  return file_name_;
}

void CppFile::Include(const Header& header) {
  headers_.insert(header);
}

void CppFile::IncludeType(const Type& type) {
  IncludeTypeHelper(this, &type);
}

void CppFile::Write(std::ostream* os) {
  std::string guard_name =
      WordList::FromUnknown(module_name_+"_"+file_name_).ToUpperCase() + "_";
  *os << kAutoGeneratedComment << endl;
  if (write_guards_) {
    *os << "#ifndef " << guard_name << endl;
    *os << "#define " << guard_name << endl;
  }
  for (std::set<Header>::iterator i = headers_.begin(), end = headers_.end();
      i != end; ++i) {
    if (i->is_local()) {
      strmfmt(*os, "#include \"{0}\"", i->name()) << endl;
    } else {
      strmfmt(*os, "#include <{0}>", i->name()) << endl;
    }
  }
  global_namespace_.Write(os);
  if (write_guards_) {
    *os << "#endif  // " << guard_name << endl;
  }
}

bool CppFile::Header::is_local() const {
  return local_;
}

const std::string& CppFile::Header::name() const {
  return name_;
}

CppFile::Header::Header(const std::string& name, bool local)
    : name_(name),
      local_(local) {
}

bool CppFile::Header::operator <(const Header& that) const {
  if (this->local_ != that.local_) {
    return int(this->local_) < int(that.local_);
  }
  return this->name_ < that.name_;
}

Namespace& CppFile::global_namespace() {
  return global_namespace_;
}

Namespace& CppFile::module_namespace() {
  return
      module_name_.empty() ?
          global_namespace() : global_namespace().nested("rpc").nested(module_name_);
}

Namespace& CppFile::types_ns() {
  return module_namespace();
}

Namespace& CppFile::requests_ns() {
  return module_namespace().nested("request");
}

Namespace& CppFile::responses_ns() {
  return module_namespace().nested("response");
}

Namespace& CppFile::notifications_ns() {
  return module_namespace().nested("notification");
}

Namespace& CppFile::NamespaceByMessageType(FunctionMessage::MessageType type) {
  switch(type) {
    case FunctionMessage::kRequest:
      return requests_ns();
    case FunctionMessage::kResponse:
      return responses_ns();
    case FunctionMessage::kNotification:
      return notifications_ns();
    default:
      assert(!"Invalid message type");
      return global_namespace();
  }
}

}  // namespace codegen

